/*
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *  Author: hakanai
 *
 */

#include <stdio.h>
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include <string.h>
#include "esp_err.h"
#include "esp_log.h"
#include "esp_vfs.h"
#include "esp_vfs_fat.h"
#include <time.h>
#include <sys/time.h>
#include <stdint.h>

#include <libsdb.h>

static const char *TAG = "app";

int64_t millis() {
  struct timeval tv;
  gettimeofday(&tv, NULL);
  return (tv.tv_sec * 1000LL + (tv.tv_usec / 1000LL));
}

static int fatfs_init() {
  static wl_handle_t s_wl_handle = WL_INVALID_HANDLE;

  ESP_LOGI(TAG, "Mounting FAT filesystem");

  // To mount device we need name of device partition, define base_path
  // and allow format partition in case if it is new one and was not formatted before
  const esp_vfs_fat_mount_config_t mount_config = {
    .max_files = 4,
    .format_if_mount_failed = false,
    .allocation_unit_size = CONFIG_WL_SECTOR_SIZE
  };

  return esp_vfs_fat_spiflash_mount_rw_wl("/spiffs", "storage", &mount_config, &s_wl_handle);
}

struct cat_rec_t {
  int num;
  char name[32];
};

char cats[102][32];

static int read_cats() {
  char buf[32];
  char *filename = "/spiffs/cat_names.txt";
  FILE *fd = fopen(filename, "r");
  int counter = 0;

  if (!fd) {
    ESP_LOGE(TAG, "Unable to open %s", filename);
    return ESP_FAIL;
  }

  while (fgets(buf, 32, fd)) {
    buf[strlen(buf) - 1] = '\0';
    strcpy(cats[counter++], buf);
  }

  fclose(fd);

  return ESP_OK;
}

static int populate_db_with_cats(sdb_t *db) {
  struct cat_rec_t r = { 0 };
  uint64_t timer = millis();


  ESP_LOGI(TAG, "Append 100 cats");

  for (int i = 0; i < 100; i++) {
    strcpy(r.name, cats[i]);
    r.num = i;
    ESP_ERROR_CHECK(sdb_append(db, &r, sizeof(struct cat_rec_t)));
  }

  ESP_LOGI(TAG, "Took: %llums", millis() - timer);

  return ESP_OK;
}

static int delete_db_cats_range(sdb_t *db, int from, int to) {
  struct cat_rec_t r = { 0 };
  int size = sdb_size(db);
  uint64_t timer = millis();
  int removed = 0;

  ESP_LOGI(TAG, "Delete test");
  ESP_LOGI(TAG, "Found %d cats in %s", size, db->filename);
  ESP_LOGI(TAG, "Lets delete in range from %d to %d", from, to);
  for (int i = 0; i < size; i++) {
    ESP_ERROR_CHECK(sdb_read(db, i, &r));
    if (r.num >= from && r.num <= to) {
      ESP_ERROR_CHECK(sdb_delete(db, i));
      removed++;
      size--;
      i--;
    }
  }

  ESP_LOGI(TAG, "Removed %d records", removed);
  ESP_LOGI(TAG, "Took: %llums", millis() - timer);

  return ESP_OK;
}

static int delete_db_cats_long(sdb_t *db, int times) {
  int size = sdb_size(db);
  uint64_t timer = millis();
  int removed = 0;

  ESP_LOGI(TAG, "Delete test");
  ESP_LOGI(TAG, "Found %d cats in %s", size, db->filename);
  for (int i = 0; i < times; i++) {
    ESP_ERROR_CHECK(sdb_delete(db, i));
    removed++;
  }

  ESP_LOGI(TAG, "Removed %d records", removed);
  ESP_LOGI(TAG, "Took: %llums", millis() - timer);

  return ESP_OK;
}

static int update_db_cats(sdb_t *db) {
  struct cat_rec_t r = { 0 };
  int size = sdb_size(db);
  uint64_t timer = millis();

  ESP_LOGI(TAG, "Update test");
  ESP_LOGI(TAG, "Found %d cats in %s", size, db->filename);
  ESP_LOGI(TAG, "Lets make their names start with J");
  for (int i = 0; i < size; i++) {
    ESP_ERROR_CHECK(sdb_read(db, i, &r));
    r.name[0] = 'J';
    ESP_ERROR_CHECK(sdb_update(db, i, &r));
  }

  ESP_LOGI(TAG, "Took: %llums", millis() - timer);

  return ESP_OK;
}

static int print_db_with_cats(sdb_t *db) {
  struct cat_rec_t r = { 0 };
  int size = sdb_size(db);
  uint64_t timer = millis();

  ESP_LOGI(TAG, "Found %d cats in %s", size, db->filename);
  for (int i = 0; i < size; i++) {
    ESP_ERROR_CHECK(sdb_read(db, i, &r));
    printf("%d> %s\n", r.num, r.name);
  }

  ESP_LOGI(TAG, "Took: %llums", millis() - timer);
  return ESP_OK;
}

static int read_db_with_cats(sdb_t *db) {
  struct cat_rec_t r = { 0 };
  int size = sdb_size(db);
  uint64_t timer = millis();

  ESP_LOGI(TAG, "Read test");
  ESP_LOGI(TAG, "Found %d cats in %s", size, db->filename);
  for (int i = 0; i < size; i++) {
    ESP_ERROR_CHECK(sdb_read(db, i, &r));
  }

  ESP_LOGI(TAG, "Took: %llums", millis() - timer);
  return ESP_OK;
}

void app_main(void) {
  sdb_t db;

  ESP_ERROR_CHECK(fatfs_init());
  ESP_ERROR_CHECK(sdb_open("/spiffs/cat_names.sdb", &db));

  read_cats();
/*  ESP_ERROR_CHECK(populate_db_with_cats(&db));*/
/*  ESP_ERROR_CHECK(print_db_with_cats(&db));*/

  for (int i = 0; i < 1; i++) {
    ESP_ERROR_CHECK(populate_db_with_cats(&db));
    ESP_LOGI(TAG, "Added %i/1000", i);
  }

  ESP_ERROR_CHECK(read_db_with_cats(&db));

/*  ESP_ERROR_CHECK(delete_db_cats_range(&db, 10, 90));*/
  ESP_ERROR_CHECK(delete_db_cats_long(&db, 10));
  ESP_ERROR_CHECK(update_db_cats(&db));
  ESP_ERROR_CHECK(print_db_with_cats(&db));
  ESP_ERROR_CHECK(sdb_close(&db));

  while (1) {
    vTaskDelay(5000 / portTICK_PERIOD_MS);
  }
}

